#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// NETWORK
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


// CONFIGURATION VARIABLES
#define MAX_HOST_NAME 256

// ERROR MESSAGE MACROS
#define ERROR_POPULATE "error_populate: populate of forwarding table failed"

// HOST ELEMENT STRUCTURE
typedef struct Host_Element
{
    char host_name[MAX_HOST_NAME];
    char IP_name[MAX_HOST_NAME];
    int port;

} Host_Element;

// FORWARD TABLE ELEMENT STRUCTURE
typedef struct Table_Element
{
    // Emulator
    Host_Element emulator;

    // Destination
    Host_Element destination;

    // Next hop
    Host_Element next_hop;

    int delay;
    int drop_prob;

    // To implement the linked list
    struct Table_Element *next_element;

} Table_Element;

// FORWARD TABLE STRUCTURE
typedef struct Forward_Table
{
    Host_Element source;
    Table_Element *head;
    Table_Element *tail;
    int num_elements;

} Forward_Table;

Forward_Table* init_table(Forward_Table *table, int src_port)
{
    char hostname[256];
    gethostname(hostname, sizeof(hostname));

    table = malloc(sizeof(Forward_Table));

    memcpy(table->source.host_name, hostname, strlen(hostname));
    table->source.port = src_port;

    // Create IP names (using ugly code)
    struct hostent *tmp_host = gethostbyname(hostname);
    struct in_addr *tmp_addr = (struct in_addr*)tmp_host->h_addr;
    char *tmp_str = inet_ntoa(*tmp_addr);
    memcpy(table->source.IP_name, tmp_str, strlen(tmp_str));

    table->head = NULL;
    table->tail = NULL;
    table->num_elements = 0;

    return table;
}

// Need to do a deep delete (TODO: FIX!)
void delete_table(Forward_Table *table)
{
    Table_Element *ptr = table->head;
    Table_Element *tmp;

    while(ptr != NULL)
    {
        tmp = ptr->next_element;
	free(ptr);
	ptr = tmp;
    }

    free(table);
}

// Thanks to the awful work that was done in populate_table(),
// searching for a match is now relatively easy...
// search for a forwarding match
// return -1 if error, 0 if success
Table_Element* scan_table(Forward_Table *table, char *dest_IP_name, int dest_port)
{
    Table_Element *ptr = table->head;
    while(ptr != NULL)
    {
        // Check for matching destination IP string names and ports
        if(strcmp(dest_IP_name, ptr->destination.IP_name) == 0 &&
            dest_port == ptr->destination.port &&
       strcmp(table->source.IP_name, ptr->emulator.IP_name) == 0 &&
            table->source.port == ptr->emulator.port)
        {
            return ptr;
	}

        ptr = ptr->next_element;
    }

    // If nothing was found
    return NULL;
}

// return -1 if error, 0 if success
int populate_table(Forward_Table *table, char *filename)
{
    FILE *forward_file;
    int status;
    Table_Element *ptr;

    // If table already has elements in it, return error
    if (table->head != NULL || table->tail != NULL)
    {
        return -1;
    }

    // Open the file containing the forwarding information
    forward_file = fopen(filename,"r");

    if (forward_file == NULL){
        return -1;
    }

    // Make the head element
    table->head = malloc(sizeof(Table_Element));
    status = fscanf(forward_file, "%s %d %s %d %s %d %d %d",
                    table->head->emulator.host_name,
                    &table->head->emulator.port,
                    table->head->destination.host_name,
                    &table->head->destination.port,
                    table->head->next_hop.host_name,
                    &table->head->next_hop.port,
                    &table->head->delay, &table->head->drop_prob);
    table->head->next_element = NULL;
    table->tail = table->head;
    table->num_elements++;

    // Create IP names (using ugly code)
    struct hostent *tmp_host = gethostbyname(table->head->emulator.host_name);
    struct in_addr *tmp_addr = (struct in_addr*)tmp_host->h_addr;
    char *tmp_str = inet_ntoa(*tmp_addr);
    memcpy(table->head->emulator.IP_name, tmp_str, strlen(tmp_str));

    tmp_host = gethostbyname(table->head->destination.host_name);
    tmp_addr = (struct in_addr*)tmp_host->h_addr;
    tmp_str = inet_ntoa(*tmp_addr);
    memcpy(table->head->destination.IP_name, tmp_str, strlen(tmp_str));

    tmp_host = gethostbyname(table->head->next_hop.host_name);
    tmp_addr = (struct in_addr*)tmp_host->h_addr;
    tmp_str = inet_ntoa(*tmp_addr);
    memcpy(table->head->next_hop.IP_name, tmp_str, strlen(tmp_str));

    // Set ptr to head
    ptr = table->head;

    while(status != EOF)
    {
        // Make the next element
	Table_Element *tmp = malloc(sizeof(Table_Element));
        status = fscanf(forward_file, "%s %d %s %d %s %d %d %d",
                   tmp->emulator.host_name, &tmp->emulator.port,
                   tmp->destination.host_name, &tmp->destination.port,
                   tmp->next_hop.host_name, &tmp->next_hop.port,
                   &tmp->delay, &tmp->drop_prob);

	// Kind of stupid, but gets the job done for now...
        if(status == EOF)
        {
            free(tmp);
            continue;
	}

        // Create IP names (using ugly code)
        tmp_host = gethostbyname(tmp->emulator.host_name);
        tmp_addr = (struct in_addr*)tmp_host->h_addr;
        tmp_str = inet_ntoa(*tmp_addr);
        memcpy(tmp->emulator.IP_name, tmp_str, strlen(tmp_str));

        tmp_host = gethostbyname(tmp->destination.host_name);
        tmp_addr = (struct in_addr*)tmp_host->h_addr;
        tmp_str = inet_ntoa(*tmp_addr);
        memcpy(tmp->destination.IP_name, tmp_str, strlen(tmp_str));

        tmp_host = gethostbyname(tmp->next_hop.host_name);
        tmp_addr = (struct in_addr*)tmp_host->h_addr;
        tmp_str = inet_ntoa(*tmp_addr);
        memcpy(tmp->next_hop.IP_name, tmp_str, strlen(tmp_str));

        // (Every day I'm) Ptr shuffling
        ptr->next_element = tmp;
        ptr = ptr->next_element;
        ptr->next_element = NULL;
	table->tail = ptr;

        table->num_elements++;
    }

    return 0;
}

void dump_table(Forward_Table *table)
{
    printf("\033[34mEMULATOR LOADED THE FOLLOWING FORWARD TABLE:\033[1;34m\n> size:\t\t%d\n", table->num_elements);

    Table_Element *ptr = table->head; 

    int i;
    for(i = 0; i < table->num_elements; i++)
    {
        printf("%s %d %s %d %s %d %d %d\n\t[%s] [%s] [%s]\n", 
	       ptr->emulator.host_name, 
               ptr->emulator.port,
               ptr->destination.host_name,
               ptr->destination.port,
               ptr->next_hop.host_name,
               ptr->next_hop.port,
               ptr->delay, ptr->drop_prob,
               ptr->emulator.IP_name,
               ptr->destination.IP_name,
               ptr->next_hop.IP_name);

	ptr = ptr->next_element;
    }

    printf("\033[0m\n");
}
